#include "rtc.h"

RTC_DateTypeDef RTC_DateStructure;

const uint8_t week_table[] = {0, 3, 3, 6, 1, 4, 6, 2, 5, 0, 3, 5}; // 计算星期

/**
 * 首次或者完全掉电过
 */
void RTC_First_Config(void)
{
    RCC_APB1PeriphClockCmd(RCC_APB1Periph_PWR | RCC_APB1Periph_BKP, ENABLE); // 启用PWR和BKP的时钟(APB1)
    PWR_BackupAccessCmd(ENABLE);                                             // 后备域解锁
    BKP_DeInit();                                                            // 备份寄存器模块复位
    RCC_LSEConfig(RCC_LSE_ON);                                               // 外部32.768KHZ晶振开启
    while (RCC_GetFlagStatus(RCC_FLAG_LSERDY) == RESET)
        ;                                   // 等待时钟稳定
    RCC_RTCCLKConfig(RCC_RTCCLKSource_LSE); // RTC时钟源配置成LSE（外部低速晶振32.768KHZ）
    RCC_RTCCLKCmd(ENABLE);                  // RTC开启
    RTC_WaitForSynchro();                   // 开启后需要等待APB1时钟与RTC时钟同步，才能读写寄存器
    RTC_WaitForLastTask();                  // 读写寄存器前，要确定上一个操作已经结束
    RTC_SetPrescaler(32767);                // 设置RTC分频器，使RTC时钟为1Hz,RTC period = RTCCLK/RTC_PR = (32.768 KHz)/(32767+1)
    RTC_ITConfig(RTC_IT_SEC, DISABLE);      // 使能秒中断
    RTC_WaitForLastTask();                  // 等待寄存器写入完成
}
void RTC_Config(void)
{
    // 在BKP的后备寄存器1中，存了一个特殊字符0xA5A5
    // 第一次上电或后备电源掉电后，该寄存器数据丢失，表明RTC数据丢失，需要重新配置
    if (BKP_ReadBackupRegister(BKP_DR1) != 0xA5A5) // 判断寄存数据是否丢失
    {
        RTC_First_Config();                       // 重新配置RTC
        BKP_WriteBackupRegister(BKP_DR1, 0xA5A5); // 配置完成后，向后备寄存器中写特殊字符0xA5A5
    }
    else
    {
        // 若后备寄存器没有掉电，则无需重新配置RTC
        // 这里我们可以利用RCC_GetFlagStatus()函数查看本次复位类型
        if (RCC_GetFlagStatus(RCC_FLAG_PORRST) != RESET)
        {
            // 这是上电复位
        }
        else if (RCC_GetFlagStatus(RCC_FLAG_PINRST) != RESET)
        {
            // 这是外部RST管脚复位
        }
        RCC_ClearFlag(); // 清除RCC中复位标志

        // 虽然RTC模块不需要重新配置，且掉电后依靠后备电池依然运行
        // 但是每次上电后，还是要使能RTCCLK
        RCC_RTCCLKCmd(ENABLE); // 使能RTCCLK
        RTC_WaitForSynchro();  // 等待RTC时钟与APB1时钟同步

        // 当不使用RTC秒中断，可以屏蔽下面2条
        //        RTC_ITConfig(RTC_IT_SEC, ENABLE);//使能秒中断
        //        RTC_WaitForLastTask();//等待操作完成
    }
#ifdef RTCClockOutput_Enable
    RCC_APB1PeriphClockCmd(RCC_APB1Periph_PWR | RCC_APB1Periph_BKP, ENABLE);
    PWR_BackupAccessCmd(ENABLE);
    BKP_TamperPinCmd(DISABLE);
    BKP_RTCOutputConfig(BKP_RTCOutputSource_CalibClock);
#endif
}

/**
 * 秒中断
 */
void RTC_IRQHandler(void)
{
    if (RTC_GetITStatus(RTC_IT_SEC) != RESET)
    {
    }
    RTC_ClearITPendingBit(RTC_FLAG_SEC);
    RTC_WaitForLastTask(); // 等待寄存器写入完成
}

/**
 * 闹钟中断
 */
void RTCAlarm_IRQHandler(void)
{
    if (RTC_GetITStatus(RTC_IT_ALR) != RESET)
    {
    }
    RTC_ClearITPendingBit(RTC_FLAG_ALR);
    RTC_WaitForLastTask(); // 等待寄存器写入完成
}

/**
 * @brief 按年月日计算星期(只允许1901-2099年)
 * @param year
 * @param month
 * @param day
 * @return uint8_t 返回值0代表周日，[1-6]代表周一至周六
 */
uint8_t RTC_GetWeek(uint16_t year, uint8_t month, uint8_t day)
{
    uint16_t temp2;
    uint8_t yearH, yearL;
    yearH = year / 100;
    yearL = year % 100;
    if (yearH > 19)
    {
        yearL += 100; // 如果为21世纪,年份数加100
    }
    // 所过闰年数只算1900年之后的
    temp2 = yearL + yearL / 4;
    temp2 = temp2 % 7;
    temp2 = temp2 + day + week_table[month - 1];
    if (yearL % 4 == 0 && month < 3)
    {
        temp2--;
    }
    return (temp2 % 7);
}

/**
 * @note 北京时间
 */
uint8_t RTC_SetDateTime(uint16_t year, uint8_t month, uint8_t day, uint8_t hour, uint8_t min, uint8_t sec)
{

    time_t time_cnt; // s
    struct tm time_date;

    time_date.tm_year = year - 1900;
    time_date.tm_mon = month - 1;
    time_date.tm_mday = day;
    time_date.tm_hour = hour;
    time_date.tm_min = min;
    time_date.tm_sec = sec;
    time_cnt = mktime(&time_date) - 8 * 60 * 60; // 日期时间转换成UTC秒; (北京时间东8区-8小时的秒数)
    RTC_First_Config();                          // 重新初始化时钟
    BKP_WriteBackupRegister(BKP_DR1, 0xA5A5);    // 配置完成后，向后备寄存器中写特殊字符0xA5A5
    RTC_SetCounter(time_cnt);                    // 把换算好的计数器值写入
    RTC_WaitForLastTask();                       // 等待写入完成
    return 0;
}

/**
 * @note 北京时间
 */
uint8_t RTC_GetDateTime(void)
{
    time_t time_cnt;
    struct tm time_date;
    time_cnt = RTC_GetCounter() + 8 * 60 * 60; // (UTC秒+8小时的秒数)=北京时间
    time_date = *(localtime(&time_cnt));
    RTC_DateStructure.year = time_date.tm_year + 1900;
    RTC_DateStructure.month = time_date.tm_mon + 1;
    RTC_DateStructure.day = time_date.tm_mday;
    RTC_DateStructure.hour = time_date.tm_hour;
    RTC_DateStructure.min = time_date.tm_min;
    RTC_DateStructure.sec = time_date.tm_sec;
    RTC_DateStructure.week = RTC_GetWeek(RTC_DateStructure.year, RTC_DateStructure.month, RTC_DateStructure.day);
    return 0;
}

/**
 * 调用前先设置: RTC_SetDateTime(2023, 1, 1, 23, 59, 55);
 */
void RTC_TestCode(void)
{
    RTC_GetDateTime();
    printf("%d-%d-%d %d:%d:%d \r\n", RTC_DateStructure.year, RTC_DateStructure.month, RTC_DateStructure.day,
           RTC_DateStructure.hour, RTC_DateStructure.min, RTC_DateStructure.sec);
    delay_s(1);
}
